/**
 * Copyright (C) 2009 IcyRiver / Wesley He
 *
 * IcyRiver Lib may be copied only under the terms of the GNU General
 * Public License V1, which may be found in the IcyRiver.net source kit.
 * Please visit the IcyRiver Home Page http://icyriver.net/ for more detail.
 **/

//socket.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <time.h>
#include <stdlib.h>
#include <fcntl.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include <netdb.h>

#include "socket.h"

int g_socket_tcp_recv_data_select(int sock, void *data, const int size, \
		const int timeout, int *count)
/*{{{*/
{
	int left_bytes;
	int read_bytes;
	int res;
	int ret_code;
	unsigned char* p;
	fd_set read_set;
	struct timeval t;

	if (data == NULL)
	{
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_tcp_recv_data_select argument data is NULL.\n", \
				__FILE__,__LINE__);
#endif
		return EINVAL;
	}

	FD_ZERO(&read_set);
	FD_SET(sock, &read_set);

	read_bytes = 0;
	ret_code = 0;
	p = (unsigned char*)data;
	left_bytes = size;
	while (left_bytes > 0)
	{
		if (timeout <= 0)
		{
			res = select(sock+1, &read_set, NULL, NULL, NULL);
		}
		else
		{
			t.tv_usec = 0;
			t.tv_sec = timeout;
			//only waiting for one sock, just because select() have timeout features
			res = select(sock+1, &read_set, NULL, NULL, &t);
		}

		if (res < 0)
		{
#ifdef __DEBUG__
			fprintf(stderr,"%s,%d:g_socket_tcp_recv_data_select call select failed:%s.\n",
					__FILE__,__LINE__,strerror(errno));
#endif
			ret_code = errno != 0 ? errno : EINTR;
			break;
		}
		else if (res == 0)
		{
#ifdef __DEBUG__
			fprintf(stderr,"%s,%d:g_socket_tcp_recv_data_select call select timeout.\n",
					__FILE__,__LINE__);
#endif
			ret_code = ETIMEDOUT;
			break;
		}

		if (FD_ISSET(sock, &read_set))
		{
			read_bytes = read(sock, p, left_bytes);
			if (read_bytes < 0)
			{
#ifdef __DEBUG__
				fprintf(stderr,"%s,%d:g_socket_tcp_recv_data_select call read failed:%s.\n",
						__FILE__,__LINE__,strerror(errno));
#endif
				ret_code = errno != 0 ? errno : EINTR;
				break;
			}
			if (read_bytes == 0)
			{
#ifdef __DEBUG__
				fprintf(stderr, "%s,%d:g_socket_tcp_recv_data_select call read return 0, remote close connection: %s.\n",
						__FILE__, __LINE__, strerror(errno));
#endif
				ret_code = ENOTCONN;
				break;
			}

			left_bytes -= read_bytes;
			p += read_bytes;
		}
	}

	if (count != NULL)
	{
		*count = size - left_bytes;
	}
	return ret_code;
}
/*}}}*/

int g_socket_tcp_gets(int sock, char* s, const int size, const int timeout)
	/*{{{*/
{
	int result;
	char t;
	int i=1;

	if (s == NULL || size <= 0)
	{
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_tcp_gets argument is illegal.\n",
				__FILE__,__LINE__);
#endif
		return EINVAL;
	}

	while (i < size)
	{
		result = g_socket_tcp_recv_data_select(sock, &t, 1, timeout, NULL);
		if (result != 0)
		{
#ifdef __DEBUG__
			fprintf(stderr,"%s,%d:g_socket_tcp_gets call g_socket_tcp_recv_data_select failed.\n",
					__FILE__,__LINE__);
#endif
			*s = 0;
			return result;
		}

		if (t == '\r')
		{
			continue;
		}

		if (t == '\n')
		{
			*s = t;
			s++;
			*s = 0;
			return 0;
		}

		*s = t;
		s++,i++;
	}

	*s = 0;
	return 0;
}
/*}}}*/

int g_socket_accept_select(int sock, const int timeout, int *err_no)
	/*{{{*/
{
	struct sockaddr_in inaddr;
	unsigned int sockaddr_len;
	fd_set read_set;
	struct timeval t;
	int result;

	//use select() just because timeout
	if (timeout > 0)
	{
		t.tv_usec = 0;
		t.tv_sec = timeout;

		FD_ZERO(&read_set);
		FD_SET(sock, &read_set);

		result = select(sock+1, &read_set, NULL, NULL, &t);
		if (result == 0)  //timeout
		{
			*err_no = ETIMEDOUT;
#ifdef __DEBUG__
			fprintf(stderr,"%s,%d:g_socket_accept_select select timeout: %s.\n",
					__FILE__, __LINE__,strerror(*err_no) );
#endif
			return -1;
		}
		else if (result < 0) //error
		{
			*err_no = errno != 0 ? errno : EINTR;
#ifdef __DEBUG__
			fprintf(stderr,"%s,%d:g_socket_accept_select select failed: %s.\n",
					__FILE__, __LINE__,strerror(*err_no) );
#endif
			return -1;
		}
	}

	sockaddr_len = sizeof(inaddr);
	result = accept(sock, (struct sockaddr*)&inaddr, &sockaddr_len);
	if (result < 0)
	{
		*err_no = errno != 0 ? errno : EINTR;
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_accept_select accept failed: %s.\n",
				__FILE__, __LINE__,strerror(*err_no) );
#endif
		return result;
	}
	else
	{
		*err_no = 0;
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_accept_select accept failed: %s.\n",
				__FILE__, __LINE__,strerror(*err_no) );
#endif
		return result;
	}

	return result;
}
/*}}}*/

int g_socket_socket_server(const char *bind_ipaddr, const int port, int *err_no)
	/*{{{*/
{
	struct sockaddr_in bindaddr;
	int sock;
	int result;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 0)
	{
		*err_no = errno != 0 ? errno : EMFILE;
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_socket_server socket create failed: %s.\n",
				__FILE__, __LINE__,strerror(*err_no) );
#endif
		return -1;
	}

	result = 1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &result, sizeof(int)) < 0)
	{
		*err_no =  errno != 0 ? errno : ENOMEM;
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_socket_server socket set failed: %s.\n",
				__FILE__, __LINE__,strerror(*err_no) );
#endif
		close(sock);
		return -2;
	}

	bindaddr.sin_family = AF_INET;
	bindaddr.sin_port = htons(port);
	if (bind_ipaddr == NULL || *bind_ipaddr == '\0')
	{
		bindaddr.sin_addr.s_addr = INADDR_ANY;
	}
	else
	{
		if (inet_aton(bind_ipaddr, &bindaddr.sin_addr) == 0)
		{
			*err_no = EINVAL;
#ifdef __DEBUG__
			fprintf(stderr,"%s,%d:g_socket_socket_server invalid ip addr bind_ipaddr: %s.\n",
					__FILE__, __LINE__, strerror(*err_no));
#endif
			close(sock);
			return -3;
		}
	}

	result = bind(sock, (struct sockaddr*)&bindaddr, sizeof(bindaddr));
	if (result < 0)
	{
		*err_no =  errno != 0 ? errno : ENOMEM;
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_socket_server bind port %d failed: %s.\n",
				__FILE__, __LINE__, port, strerror(*err_no) );
#endif
		close(sock);
		return -4;
	}

	result = listen(sock, 128);
	if (result < 0)
	{
		*err_no =  errno != 0 ? errno : EINVAL;
#ifdef __DEBUG__
		fprintf(stderr,"%s,%d:g_socket_socket_server listen port %d failed: %s.\n",
				__FILE__, __LINE__, port, strerror(*err_no) );
#endif
		close(sock);
		return -5;
	}

	*err_no = 0;
	return sock;
}
/*}}}*/

int g_socket_connect_server_by_ip(int sock, char* ip, short port, int *err_no)
/*{{{*/
{
	int result;
	struct sockaddr_in addr;

	addr.sin_family = PF_INET;
	addr.sin_port = htons(port);
	result = inet_aton(ip, &addr.sin_addr);
	if (result == 0 )
	{
		*err_no = EINVAL;
#ifdef __DEBUG__
		fprintf(stderr,"file: %s, line: %d:g_socket_connect_server_by_ip call inet_aton failed: %s.\n",
				__FILE__, __LINE__, strerror(*err_no));
#endif
		return -1;
	}

	result = connect(sock, (const struct sockaddr*)&addr, sizeof(addr));
	if (result < 0)
	{
		*err_no = errno != 0 ? errno : EINTR;
#ifdef __DEBUG__
		fprintf(stderr,"file: %s, line: %d: g_socket_connect_server_by_ip %s:%d, call connect is failed: %s.\n",
				__FILE__, __LINE__, ip, port, strerror(*err_no));
#endif
		return result;
	}

	return result;
}
/*}}}*/

int g_socket_connect_server(char* ip, short port, int *err_no )
/*{{{*/
{
	int result;
	int sock;

	sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		*err_no = errno != 0 ? errno : EPERM;
#ifdef __DEBUG__
		fprintf(stderr,"file: %s, line: %d:g_socket_connect_server socket create failed: %s.\n",
				__FILE__, __LINE__, strerror(*err_no));
#endif
		return -1;
	}

	if ((result=g_socket_connect_server_by_ip(sock, ip, port, err_no)) < 0)
	{
		close(sock);
		return -2;
	}

	return sock;
}
/*}}}*/
